package com.linking.web.repository;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.linking.web.manager.redis.RedisSelfUtils;
import com.linking.web.pojo.bo.PageBO;
import com.linking.web.pojo.po.BasePO;
import com.linking.web.pojo.po.BasePO.RefreshType;
import com.linking.web.pojo.po.EmptyPO;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Resource;
import org.springframework.stereotype.Repository;

/**
 * @Author YaoWeiXin
 * @Date 2020/6/1 16:20
 * @Description 集合数据的仓库基类
 */
@Repository
public abstract class AbstractListRepository<T extends BasePO> extends BaseRepository {

  @Resource
  RedisSelfUtils redisUtils;

  /**
   * 获得数据库名
   *
   * @return 数据库名
   */
  protected abstract String getDbName();

  /**
   * 获得数据库集合名
   *
   * @return 数据库集合名
   */
  protected abstract String getCollection();

  /**
   * 是否需要保存
   *
   * @return true|false
   */
  protected abstract Boolean isCache();

  /**
   * 根据主键编号从数据库查询对象
   *
   * @param id 主键编号
   * @return 结果
   */
  protected abstract T queryByPrimary(String id);

  /**
   * 查询集合
   *
   * @param id 主键编号
   * @return 结果
   */
  public abstract List<T> queryList(String id);

  /**
   * 分页查询集合
   *
   * @param id     主键编号
   * @param pageBo 分页信息
   * @return 结果
   */
  public abstract List<T> queryPageList(String id, PageBO pageBo);

  /**
   * 在从数据库查询出对象后
   *
   * @param t 范型对象
   */
  protected abstract void afterQuery(T t);

  /**
   * DB层插入对象
   *
   * @param t 范型对象
   */
  protected abstract void insert(T t);

  /**
   * DB层修改对象
   *
   * @param t 范型对象
   */
  protected abstract void update(T t);

  /**
   * DB层移除对象
   *
   * @param t 范型对象
   */
  protected abstract void delete(T t);

  /**
   * 获得对象的redis KEY
   *
   * @param id 主键编号
   * @return 结果
   */
  protected abstract String getRedisKey(String id);

  /**
   * 超时时间
   *
   * @return 结果
   */
  protected abstract Long expireTime();

  /**
   * 获得对象唯一编号
   *
   * @param t 范型对象
   * @return 结果
   */
  protected abstract String getId(T t);

  /**
   * 获得对象集合组编号
   *
   * @param t 范型对象
   * @return 结果
   */
  protected abstract String getGroupId(T t);

  /**
   * 在仓库刷新对象后
   *
   * @param refreshType 刷新类型
   * @param t           范型对象
   */
  protected abstract void afterRefresh(RefreshType refreshType, T t);

  /**
   * 对象保存进缓存
   *
   * @param t 对象
   */
  private void toCache(T t) {
    if (isCache()) {
      String key = getRedisKey(getGroupId(t));
      redisUtils.hmSet(key, getId(t), t);
      redisUtils.expire(key, expireTime());
    }
  }

  /**
   * 对象集合保存进缓存
   *
   * @param list 对象集合
   */
  private void toCacheAll(String groupId, List<T> list) {
    if (isCache() && list != null && list.size() > 0) {
      String key = getRedisKey(groupId);
      Map<String, Object> map = Maps.newHashMap();
      for (T t : list) {
        map.put(getId(t), t);
      }
      redisUtils.hmSetAll(key, map);
      redisUtils.expire(key, expireTime());
    }
  }

  /**
   * 从缓存中获取对象集合
   *
   * @param groupId 组编号
   * @return 对象集合
   */
  @SuppressWarnings("unchecked")
  private List<T> fromCache(String groupId) {
    if (isCache()) {
      String key = getRedisKey(groupId);
      List<T> list = Lists.newArrayList();
      List<Object> objs = redisUtils.hGetAll(key);
      for (Object obj : objs) {
        list.add((T) obj);
      }
      return list;
    }
    return null;
  }

  /**
   * 从缓存中获取对象
   *
   * @param groupId 组编号
   * @param id      主键编号
   * @return 对象
   */
  @SuppressWarnings("unchecked")
  private T fromCache(String groupId, String id) {
    if (isCache()) {
      List<T> objs = fromCache(groupId);
      if (objs != null) {
        for (Object obj : objs) {
          T t = (T) obj;
          if (id.equals(getId(t))) {
            return t;
          }
        }
      }
    }
    return null;
  }

  /**
   * 从缓存中删除对象
   *
   * @param t 对象
   */
  private void removeCache(T t) {
    if (isCache()) {
      String key = getRedisKey(getGroupId(t));
      redisUtils.hmDel(key, getId(t));
    }
  }

  /**
   * 判断是否是空对象
   *
   * @param t 对象
   * @return true|false
   */
  private Boolean isEmptyCache(T t) {
    return t instanceof EmptyPO;
  }

  /**
   * 设置临时缓存，防止缓存穿透
   *
   * @param groupId 组编号
   * @param id      主键编号
   */
  private void setEmptyCache(String groupId, String id) {
    if (isCache()) {
      // 临时存入缓存，防止缓存穿透
      String key = getRedisKey(groupId);
      redisUtils.hmSet(key, id, new EmptyPO());
    }
  }

  /**
   * 获取对象(先从缓存取，没有从数据库中查)
   *
   * @param groupId 组编号
   * @param id      主键编号
   * @return 对象
   */
  public Optional<T> getAlways(String groupId, String id) {
    T t = fromCache(groupId, id);
    if (isEmptyCache(t)) {
      return Optional.empty();
    }
    if (t == null) {
      t = queryByPrimary(id);
      if (t != null) {
        afterQuery(t);
      } else {
        // 设置临时缓存，防止缓存穿透
        setEmptyCache(groupId, id);
      }
    }
    return Optional.ofNullable(t);
  }

  /**
   * 获取对象集合(先从缓存取，没有从数据库中查)
   *
   * @param groupId 组编号
   * @return 对象
   */
  public List<T> getAlwaysList(String groupId) {
    List<T> list = fromCache(groupId);
    if (list == null || list.size() == 0) {
      list = queryList(groupId);
      if (list != null && list.size() > 0) {
        toCacheAll(groupId, list);
      }
    }
    return list == null ? Lists.newArrayList() : list;
  }

  /**
   * 对象刷新至仓库
   *
   * @param t 泛型对象
   */
  public void refresh(T t) {
    RefreshType refreshType = t.refreshType();
    switch (refreshType) {
      case Create:
        insert(t);
        break;
      case Change:
        t.setUpdateTime(t.getCurTime());
        update(t);
        break;
      case Delete:
        delete(t);
        break;
      default:
        break;
    }

    boolean needCache = t.needCache();
    boolean needClearCache = t.needClearCache();
    t.setCreate(false);
    t.setChange(false);
    t.setDelete(false);
    t.setCache(false);
    if (needCache) {
      toCache(t);
    }
    if (needClearCache) {
      removeCache(t);
    }
    afterRefresh(refreshType, t);
  }
}
